#include "pch.h"
#include "GIFRenderer.h"
#include "D2DManager.h"

using namespace GIFPlayer;

GIFRenderer::GIFRenderer(Platform::String^ fileName, UINT width, UINT height)
{
	// Initialize decoder to obtain metadata from gif file
	m_gifDecoder.reset(new GIFDecoder(fileName->Data()));
	
	// Set the surface size and then create it
	m_surfaceSize = Size(static_cast<float>(width), static_cast<float>(height));
	CreateSurfaceImageSource();
	
	// Initialize the frame cache
	m_frames_count = m_gifDecoder->GetFramesCount();
	m_cached_frames_data.resize(m_frames_count);
	m_cached_frames.resize(m_frames_count);

	// Create composite effect used for dispose method > 1
	D2DManager::GetInstance()->GetD2DDeviceContext()->CreateEffect(CLSID_D2D1Composite, &m_compositeEffect);
}

GIFRenderer::GIFRenderer(GIFDecoder* decoder)
{
	// Initialize decoder to obtain metadata from gif file
	m_gifDecoder.reset(decoder);

	// Initialize the frame cache
	m_frames_count = m_gifDecoder->GetFramesCount();
	m_cached_frames_data.resize(m_frames_count);
	m_cached_frames.resize(m_frames_count);

	// Create composite effect used for dispose method > 1
	D2DManager::GetInstance()->GetD2DDeviceContext()->CreateEffect(CLSID_D2D1Composite, &m_compositeEffect);
}

void GIFRenderer::SetSurfaceSize(UINT width, UINT height)
{
	// TODO : check the different call scenario

	// Set the surface size and then create it
	m_surfaceSize = Size(static_cast<float>(width), static_cast<float>(height));
    
	CreateSurfaceImageSource();
}


SurfaceImageSource^ GIFRenderer::CreateSurfaceImageSource()
{
    m_surfaceImageSource = nullptr;
    m_nativeImageSource = nullptr;
	if (m_surfaceImageSource == nullptr)
	{
		// Create SurfaceImageSource with the good size and transparency support
		m_surfaceImageSource = ref new SurfaceImageSource(static_cast<UINT>(m_surfaceSize.Width), static_cast<UINT>(m_surfaceSize.Height), false);
		
		// Get the SurfaceImageSourceNativeWithD2D corresponding to this surface
		IInspectable* inspectable = (IInspectable*) reinterpret_cast<IInspectable*>(m_surfaceImageSource);
		inspectable->QueryInterface(__uuidof(ISurfaceImageSourceNativeWithD2D),	(void **)&m_nativeImageSource);

		// Bind the D2D device with this native surface
		m_nativeImageSource->SetDevice(D2DManager::GetInstance()->GetD2DDevice());
	}

	return m_surfaceImageSource;
}

UINT GIFRenderer::DrawFrame(UINT frameIndex){

	if (m_nativeImageSource == nullptr)
		throw Platform::NullReferenceException::CreateException(1);

	// Check frame index is in good range
	if (frameIndex < 0 && frameIndex >= m_frames_count)
		return -1;

	ComPtr<ID2D1DeviceContext1> d2dContext = D2DManager::GetInstance()->GetD2DDeviceContext();
	FrameMetadata current_frame_data = FrameMetadata();
	ID2D1Bitmap1* current_frame = nullptr;

	// If frame is in cache then take it with data
	if (m_cached_frames[frameIndex] != nullptr && m_cached_frames_data[frameIndex].frameIndex != UINT_MAX)
	{
		current_frame_data = m_cached_frames_data[frameIndex];
		current_frame = m_cached_frames[frameIndex];
	}
	else
	{
		// else compute it
		ThrowIfFailed(m_gifDecoder->GetFrameAndMetadata(frameIndex, static_cast<UINT>(m_surfaceSize.Width), static_cast<UINT>(m_surfaceSize.Height), current_frame_data, current_frame));

		// Put the frame with its data in cache
		m_cached_frames[frameIndex] = current_frame;
		m_cached_frames_data[frameIndex] = current_frame_data;
	}

    
    RECT drawRect = { 0, 0, static_cast<LONG>(m_surfaceSize.Width), static_cast<LONG>(m_surfaceSize.Height) };
	POINT offset = { 0, 0 };
    
	ComPtr<IDXGISurface> surface;

	ThrowIfFailed(
		m_nativeImageSource->BeginDraw(drawRect, IID_IDXGISurface, &surface, &offset)
		);

	D2D1_RECT_F destRect = D2D1::RectF(0, 0, m_surfaceSize.Width, m_surfaceSize.Height);
	destRect.right += offset.x;
	destRect.left += offset.x;
	destRect.top += offset.y;
	destRect.bottom += offset.y;


	ThrowIfFailed(
		d2dContext->CreateBitmapFromDxgiSurface(
		surface.Get(),
		nullptr,
		&m_targetBitmap
		)
		);

	d2dContext->SetTarget(m_targetBitmap.Get());

	d2dContext->BeginDraw();
	d2dContext->PushAxisAlignedClip(&destRect, D2D1_ANTIALIAS_MODE_ALIASED);

	// Clear with transparent background
	d2dContext->Clear(D2D1::ColorF(0, 0, 0, 0));

	if (current_frame_data.transparent && current_frame_data.disposal <= 1)
	{
		for (UINT i = 0; i <= m_current_index; i++)
		{
			m_compositeEffect->SetInput(i, m_cached_frames[i]);
			m_compositeEffect->SetInputCount(m_current_index + 1);
		}

		D2D1_POINT_2F targetOffset = D2D1::Point2F(static_cast<float>(offset.x), static_cast<float>(offset.y));
		d2dContext->DrawImage(m_compositeEffect.Get(), targetOffset, D2D1_INTERPOLATION_MODE_LINEAR);

	}
	else
	{
		D2D1_RECT_F sourceRect = D2D1::RectF(0, 0, m_surfaceSize.Width, m_surfaceSize.Height);
		d2dContext->DrawBitmap(
			current_frame,
			&destRect, 1.0f, D2D1_INTERPOLATION_MODE_LINEAR, &sourceRect);
	}

	// Batch drawing calls and send final bitmap to the surfaceimagesource
	d2dContext->PopAxisAlignedClip();
	d2dContext->EndDraw();
	m_nativeImageSource->EndDraw();

	/*if (current_frame && !USE_CACHE)
	{
	current_frame->Release();
	current_frame = nullptr;
	}*/

	return current_frame_data.delay;
}

UINT GIFRenderer::DrawNextFrame()
{
	++m_current_index;
	if (m_current_index >= m_frames_count)
		m_current_index = 0;

	UINT rDelay = DrawFrame(m_current_index);

	return rDelay;
}

UINT GIFRenderer::DrawCurrentFrameAndGoNext()
{
	UINT rDelay = DrawFrame(m_current_index);

	++m_current_index;
	if (m_current_index >= m_frames_count)
		m_current_index = 0;

	return rDelay;
}

UINT GIFRenderer::DrawPreviousFrame()
{
	--m_current_index;
	if (m_current_index < 0)
		m_current_index = m_frames_count - 1;

	UINT rDelay = DrawFrame(m_current_index);

	return rDelay;
}

UINT GIFRenderer::DrawCurrentFrameAndGoPrevious()
{
	UINT rDelay = DrawFrame(m_current_index);

	--m_current_index;
	if (m_current_index < 0)
		m_current_index = m_frames_count - 1;

	return rDelay;
}